#ifndef OBIELLIPSOIDS_INCLUDED
#define OBIELLIPSOIDS_INCLUDED

// Eye ray origin in world space.
// Works both for orthographic and perspective cameras.
float3 WorldEye(float3 worldPos){
    if ((UNITY_MATRIX_P[3].x == 0.0) && (UNITY_MATRIX_P[3].y == 0.0) && (UNITY_MATRIX_P[3].z == 0.0)){
        return mul(UNITY_MATRIX_I_V,float4(mul(UNITY_MATRIX_V, float4(worldPos,1)).xy,0,1)).xyz;
    }else
        return _WorldSpaceCameraPos;
}

// Returns visible ellipsoid radius and offset from center, given the eye position in parameter space. 
// Works both for orthographic and perspective cameras.
float VisibleEllipsoidCircleRadius(float3 eye, out float3 m){
    if ((UNITY_MATRIX_P[3].x == 0.0) && (UNITY_MATRIX_P[3].y == 0.0) && (UNITY_MATRIX_P[3].z == 0.0)){
        m = float3(0,0,0);
        return 1;
    }else{
        float t = 1/dot(eye,eye);
        m = t * eye;
        return sqrt(1-t);
    }
}

// Performs accurate raycasting of a spherical impostor.
// Works both for orthographic and perspective cameras.
void IntersectEllipsoid_float(float3 v, float4 mapping, float3 a2, float3 a3, out float3 eyePos, out float3 eyeNormal, out float thickness, out float clipThreshold)
{
    float r2 = dot(mapping.xy, mapping.xy);

    // clip if the ray does not intersect the sphere.
    clipThreshold = r2/mapping.w;
    float iq = 1 - clipThreshold;

    float sqrtiq = sqrt(iq);
    float lambda = 1/(1 + mapping.z * sqrtiq);

    eyePos = lambda * v;
    eyeNormal = normalize(a2 + lambda * a3);

    // return gaussian-falloff thickness.
    thickness = 2 * sqrtiq * exp(-r2*2.0f);

    clipThreshold = (1 - clipThreshold > 0) ? 0 : 1.01;
}

void BuildVelocityStretchedBasis_float(float3 velocity, float stretchIntensity, float radius, out float4 t0, out float4 t1, out float4 t2)
{
    t0 = float4(UNITY_MATRIX_V[0].xyz, radius); // camera right vector
    t2 = float4(UNITY_MATRIX_V[2].xyz, radius); // camera forward vector

    float3 eyeVel = velocity - dot(velocity, t2.xyz) * t2.xyz;
    float velNorm = length(eyeVel);
    float stretchAmount = velNorm * stretchIntensity;

    // use it to lerp between velocity vector and camera right:
    t0 = float4(velNorm > 0.00001 ? eyeVel / velNorm : t0.xyz, radius * (1 + stretchAmount));
    t1 = float4(normalize(cross(t0.xyz,t2.xyz)), radius);
}

void BuildParameterSpaceMatrices_float(float4 t0, float4 t1, float4 t2, float radiusScale, out float3x3 P, out float3x3 IP)
{
    // build 3x3 orientation matrix and its inverse;
    float3x3 IO = float3x3(t0.xyz,t1.xyz,t2.xyz);
    float3x3 O = transpose(IO);

    // build 3x3 scaling matrix and its inverse:
    float3x3 S = float3x3(radiusScale*t0.w,0,0,0,radiusScale*t1.w,0,0,0,radiusScale*t2.w);
    float3x3 IS = float3x3(1/(radiusScale*t0.w),0,0,0,1/(radiusScale*t1.w),0,0,0,1/(radiusScale*t2.w));

    // build 3x3 transformation matrix and its inverse:
    P = mul((float3x3)UNITY_MATRIX_M,  mul(O,mul(S,IO)) );
    IP = mul(mul(mul(O,IS),IO), (float3x3)UNITY_MATRIX_I_M);
}

void BuildEllipsoidBillboard_float(float3 center, float3 corner, float3x3 P, float3x3 IP, out float3 worldPos, out float3 view, out float3 eye, out float radius)
{
    // eye position and quad vectors in parameter space:
    eye = mul(IP,WorldEye(center) - center);
    float3 u = normalize(cross(-eye,UNITY_MATRIX_V[1].xyz));
    float3 k = normalize(cross(-eye,u));

    // visible circle radius and offset from center in the direction of the view ray:
    float3 m;
    radius = VisibleEllipsoidCircleRadius(eye,m);

    // world position of the billboard corner, and view vector to it:
    worldPos = center + mul(P, m) + radius * (mul(P,u)* corner.x + mul(P,k)* corner.y);
    view = worldPos - WorldEye(worldPos);
}

void BuildAuxiliaryNormalVectors_float(float3 center, float3 worldPos, float3 view, float3x3 P, float3x3 IP, out float3 a2, out float3 a3)
{
    // calculate T^-2 in object space, then multiply by 
    // inverse transpose of modelview to rotate normal from object to eye. 
    // This way the normal calculated in IntersectEllipsoid() is already in view space.
    IP = mul((float3x3)UNITY_MATRIX_M,IP);
    float3x3 IT_MV = transpose(mul((float3x3)UNITY_MATRIX_I_V, (float3x3)UNITY_MATRIX_I_M));
    float3x3 IP2 = mul(IT_MV, mul((float3x3)UNITY_MATRIX_I_M,mul (IP, IP))); // UNITY_MATRIX_IT_MV

    a2 = mul(IP2,WorldEye(worldPos) - center); //T^-2 * (eye - center)
    a3 = mul(IP2,view);                        //T^-2 * A[0]
}

#endif
